﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design.Serialization;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DataStore
{
    public class DataBlock: IDisposable
    {
        /// <summary>
        /// 最大数据大小
        /// </summary>
        public long MaxDataSize { get;  set; }

        /// <summary>
        /// 数据总数
        /// </summary>
        public long DataCount { get; private set; }

        public const long DefaultMaxDataSize = 1024 * 1024 * 1024;
        /// <summary>
        /// 数据文件当前写入地址
        /// </summary>
        private long CurrentWriteDataPosition;

        /// <summary>
        /// 数据文件待写入地址
        /// </summary>
        private long NextWriteDataPosition;

        /// <summary>
        /// 索引文件当前写入地址
        /// </summary>
        private long CurrentWriteIndexPosition;

        /// <summary>
        /// 索引文件待写入地址
        /// </summary>
        private long NextWriteIndexPosition;

        /// <summary>
        /// 索引文件流
        /// </summary>
        private Stream IndexStream;

        /// <summary>
        /// 数据文件流
        /// </summary>
        private Stream DataStream;

        /// <summary>
        /// 消息坐标及长度占用字节数
        /// </summary>
        private const int IndexRecorderSize = IndexPositionSize + DataLengthSize;
        private const int IndexPositionSize = sizeof(long);
        private const int DataLengthSize = sizeof(int);

        

        public DataBlock(string dataFilePath, string indexFilePath, bool isCreateNew, long maxDataSize = DefaultMaxDataSize)
            : this(OpenFile(dataFilePath, isCreateNew, maxDataSize), OpenFile(indexFilePath, isCreateNew, null), maxDataSize)
        {
        }

        public DataBlock(Stream dataStream, Stream indexStream, long maxDataSize = DefaultMaxDataSize)
        {
            this.MaxDataSize = maxDataSize;
            this.DataStream = dataStream;
            this.IndexStream = indexStream;
            ComputerCurrentStatus();
        }

        public Span<byte> ReadData(long index)
        {
            if (index < 0 || index >= DataCount) throw new ArgumentOutOfRangeException();
            var header = ReadIndex(index);
            if (header.MessagePosition != DataStream.Position) DataStream.Seek(header.MessagePosition, SeekOrigin.Begin);
            Span<byte> datas = new byte[header.MessageLength];
            DataStream.Read(datas);
            var startLength = BitConverter.ToInt32(datas.Slice(0, DataLengthSize));
            var endLength = BitConverter.ToInt32(datas.Slice(datas.Length - DataLengthSize));
            if (startLength != endLength) throw new  Exception("数据前后长度校验失败，数据异常！");
            return datas.Slice(DataLengthSize, datas.Length - DataLengthSize * 2);
        }


        public bool WriteData(Span<byte> data)
        {
            var messageLength = data.Length + DataLengthSize * 2;
            if (NextWriteDataPosition + messageLength > MaxDataSize) return false;  //数据文件已满
            CurrentWriteDataPosition = NextWriteDataPosition;
            NextWriteDataPosition = NextWriteDataPosition + messageLength;

            if (DataStream.Position != CurrentWriteDataPosition) DataStream.Seek(CurrentWriteDataPosition, SeekOrigin.Begin);
            DataStream.Write(BitConverter.GetBytes(data.Length));
            DataStream.Write(data);
            DataStream.Write(BitConverter.GetBytes(data.Length));
            WriterIndex(CurrentWriteDataPosition, messageLength);
            return true;
        }

        public void Flush()
        {
            DataStream.Flush();
            IndexStream.Flush();
        }

        public static long ComputerDataCount(long indexStreamLength)
        {
            return indexStreamLength / IndexRecorderSize;
        }

        private void WriterIndex(long messagePosition,int messageLength)
        {
            CurrentWriteIndexPosition = NextWriteIndexPosition;
            NextWriteIndexPosition = NextWriteIndexPosition + IndexRecorderSize;
            if (CurrentWriteIndexPosition != IndexStream.Position) IndexStream.Seek(CurrentWriteIndexPosition, SeekOrigin.Begin);
            IndexStream.Write(BitConverter.GetBytes(messagePosition));
            IndexStream.Write(BitConverter.GetBytes(messageLength));
            DataCount++;
        }

        private (long MessagePosition, int MessageLength) ReadIndex(long index)
        {
            (long, int) ret;           
            var readPosition = index * IndexRecorderSize;
            if (IndexStream.Position != readPosition) IndexStream.Seek(readPosition, SeekOrigin.Begin);
            Span<byte> datas = new byte[IndexRecorderSize];
            IndexStream.Read(datas);
            ret.Item1 = BitConverter.ToInt64(datas.Slice(0, IndexPositionSize));
            ret.Item2 = BitConverter.ToInt32(datas.Slice(IndexPositionSize));
            return ret;
        }


        private void ComputerCurrentStatus()
        {
            DataCount = ComputerDataCount(IndexStream.Length);
            if (DataCount > 0)
            {
                CurrentWriteIndexPosition = (DataCount - 1) * IndexRecorderSize;
                NextWriteIndexPosition = DataCount * IndexRecorderSize;
                var header = ReadIndex(DataCount - 1);
                CurrentWriteDataPosition = header.MessagePosition;
                NextWriteDataPosition = CurrentWriteDataPosition + header.MessageLength;
            }
        }

        private  static FileStream OpenFile(string fileName, bool isCreateNew, long? initLength)
        {
            var dir = Path.GetDirectoryName(fileName);
            if (Directory.Exists(dir) == false) Directory.CreateDirectory(dir);
            FileStream fileStream = null;
            if (isCreateNew)
            {
                fileStream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
            }
            else
            {
                fileStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
            }
            if (initLength != null) fileStream.SetLength(initLength.Value);
            return fileStream;
        }

        #region 析构函数

        private bool isdisposed;

        public void Close()
        {
            Dispose();
        }
                     
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (isdisposed == false)
            {
                if (disposing)  //释放托管资源
                {
                    if (DataStream != null) DataStream.Dispose();
                    if (IndexStream != null) IndexStream.Dispose();
                }
                //释放非托管资源
                //do something

                isdisposed = true;
            }
        }

        ~DataBlock()
        {
            Dispose(false);
        }

        #endregion

    }
}
